# -*- coding: utf-8 -*-
"""
Created on Tue Jun 10 17:47:10 2014

@author: Adrien
"""

import PluginManager
import traceback


class PyFusion:
    
    def __init__(self):
        self.initialDatabasePath = "./Databases/myGlobalDatabase.xls"
        self.initialDiseasesPath = "./Databases/DISEASES.txt"
        self.databasePath = None
        self.diseasesPath = None
        self.nameGeneA = None
        self.nameGeneB = None

    def initPluginManager(self):
        self.pluginManager = PluginManager.PluginManager()     
        self.pluginManager.setPluginPath('./Plugins/')
        self.pluginManager.reloadPlugins()
         
    def errorHandler(self ,exc, val, tb):
        tb = traceback.format_exc()
        print tb
    
    def createDatabase(self, databasePath, diseasesFile=""):
        if len (self.pluginManager.importedDatabaseHandlerPlugins) ==0:
            return
        self.databaseManager = DatabaseManager()
        if diseasesFile !="":
            self.databaseManager.initDiseases(diseasesFile)
        self.databaseManager.setDatabasePath(databasePath)
        self.databaseManager.createDatabase(self.pluginManager.importedDatabaseHandlerPlugins)
        self.databaseManager.writeDatabase()
    
    def readDatabase(self,databasePath):
        self.databaseManager = DatabaseManager()
        self.databaseManager.setDatabasePath(databasePath)
        self.databaseManager.readDatabases()
    
    def makeRequest(self,geneA,geneB=None):
            if geneA!=None:
                return self.databaseManager.searchFusion(geneA,geneB)
            if geneA == None:
                return self.databaseManager.searchFusion(geneB,geneA)
    def setErrorHandler(self,func):
        self.errorHandler=func
    
    def handleError(self,exc, val, tb):
        self.errorHandler(exc, val, tb)

    def getDatabaseBasePlugins(self):
        return self.pluginManager.getPlugins();

    def saveDatabasePath(self):
        configFile = open("config.conf", "w")
        if self.databasePath != None and self.databasePath != "":
            configFile.write(self.databasePath)
        else:
            configFile.write("")
        configFile.close()

    def restoreDatabasePath(self):
        try:
            configFile = open("config.conf", "r")
            path = configFile.readline()
            if path != "":
                self.databasePath = path        
            configFile.close()
        except IOError:
            pass


class Gene:
    
    def __init__(self,name="",function="",chromosome="",disease="",locus=""):
      self.name=name
      self.function=function
      self.chromosome=chromosome
      self.disease=disease
      self.locus=locus

    
    def __lt__(self, other):
        return self.name<other.name

    def __le__(self, other):
        return self.name<=other.name

    def __eq__(self, other):
       if self.name ==other.name:
           if other.function:
               words = other.function.split()
               for word in words:
                   if  self.function.find(word)==-1:
                       self.function+=word + " "
           if self.chromosome == "":
               self.chromosome =other.chromosome
           if other.disease:
               words = other.disease.split()
               for word in words:
                   if  self.disease.find(word)==-1:
                       self.disease+=word + " "
           if self.locus =="":
               self.locus+=other.locus
           return True
       else:
           return False

    def __ge__(self, other):
        return self.name>=other.name

    def __gt__(self, other):
        return self.name>other.name

    def __ne__(self, other):
        return not self.__eq__(other)
        
    def __hash__(self):
        return hash(self.name)
    
class Fusion:
    
    class Information:
        def __init__(self,breakpoint, strand, fusionDirection):
            self.breakpoint=breakpoint
            self.strand=strand
            self.fusionDirection=fusionDirection
    
    def __init__(self,geneA="",geneB="",breakpointA="", strandA="", fusionDirectionA="",breakpointB="", strandB="", fusionDirectionB="",notes=""):
        self.geneA=geneA
        self.geneB=geneB
        self.informationA=self.Information(breakpointA, strandA, fusionDirectionA)
        self.informationB=self.Information(breakpointB, strandB, fusionDirectionB)
        self.notes=notes
    
    def __lt__(self, other):
        return self.geneA<other.geneA

    def __le__(self, other):
        return self.geneA<= other.geneA
        
    def __eq__(self, other):
       if self.geneA ==other.geneA and self.geneB == other.geneB:
           if self.informationA.breakpoint=="":
               self.informationA.breakpoint+=other.informationA.breakpoint
           if self.informationA.fusionDirection=="":
               self.informationA.fusionDirection+=other.informationA.fusionDirection
           if self.informationA.strand=="":
               self.informationA.strand+=other.informationA.strand
           if self.informationB.breakpoint=="":
               self.informationB.breakpoint+=other.informationB.breakpoint
           if self.informationB.fusionDirection=="":
               self.informationB.fusionDirection+=other.informationB.fusionDirection
           if self.informationB.strand=="":
               self.informationB.strand+=other.informationB.strand
           if other.notes:
               words = other.notes.split()
               for word in words:
                   if  self.notes.find(word)==-1:
                       self.notes+=word + " "
           return True
       else:
           return False

    def __ge__(self, other):
        return self.geneA>self.geneA

    def __gt__(self, other):
        return self.geneA>=self.geneA

    def __ne__(self, other):
        return not self.__eq__(other)
    
    def __hash__(self):
        if self.informationA.breakpoint =="" and self.informationB.breakpoint=="":
            return hash((self.geneA,self.geneB)) & hash((self.geneB,self.geneA))
        return hash((self.geneA,self.geneB,self.informationA.breakpoint,self.informationB.breakpoint)) & hash((self.geneB,self.geneA,self.informationB.breakpoint,self.informationA.breakpoint))

class DatabaseManager:    
    
    def __init__(self):
        self.diseasesList=[]
        self.databasePath=""
        self.genes=[]
        self.genesHeaders=["name","function","chromosome","disease","locus"]
        self.fusions=[]
        self.fusionsHeaders=["idGeneA","idGeneB","breakpointA", "strandA", "fusionDirectionA","breakpointB", "strandB", "fusionDirectionB","notes"]
        
    def setDatabasePath(self,databasePath):
        self.databasePath=databasePath

    def getDatabasePath(self):
        return self.databasePath   
    
    def initDiseases(self,diseaseFile):
        self.diseasesList=[]
        with open(diseaseFile) as fp:
            for line in fp:
                if line != "\n" and line!="\r\n":
                    disease = line.strip()
                    self.diseasesList.append(disease)
                
    def searchGene(self,geneA):
        for gene in self.genes:
            if str(gene.name) == geneA:
                return gene
        return None
        
    def searchFusion (self,geneA,geneB):
        fusionFound=[]
        if geneB==None:
            for fusion in self.fusions:
                if fusion.geneA ==geneA:
                    fusionFound.append(fusion)
                if  fusion.geneB==geneA:
                    newFusion = Fusion(fusion.geneB,fusion.geneA,fusion.informationB.breakpoint,fusion.informationB.strand,fusion.informationB.fusionDirection,fusion.informationA.breakpoint,fusion.informationA.strand, fusion.informationA.fusionDirection,fusion.notes)
                    fusionFound.append(newFusion)
        else:
            for fusion in self.fusions:
                if (fusion.geneA == geneA and fusion.geneB== geneA):
                        fusionFound.append(fusion)
                if (fusion.geneA==geneB and fusion.geneB==geneA):
                        newFusion = Fusion(fusion.geneB,fusion.geneA,fusion.informationB.breakpoint,fusion.informationB.strand,fusion.informationB.fusionDirection,fusion.informationA.breakpoint,fusion.informationA.strand, fusion.informationA.fusionDirection,fusion.notes)
                        fusionFound.append(newFusion)
        geneAFound = self.searchGene(geneA)
        if geneB!=None:
            geneBFound = self.searchGene(geneB)
        else:
            geneBFound=None
        
        return (geneAFound,geneBFound,fusionFound)
        
        
    def isGeneListed(self,geneName):
        matches = [x for x in self.genes if x.name==geneName]
        if len(matches) == 0:
            return False
        else:
            return matches[0].id_gene
    
    def createDatabase(self,databasePlugins):
        import Queue
        import threading
        threads=[]
        results=[]
        self.processQueue = Queue.Queue()
        for key in databasePlugins.keys():
            pluginInstance =databasePlugins.get(key)
            worker = threading.Thread(target=self.readDatabase, args=(pluginInstance,))
            worker.setDaemon(True)
            worker.start()
            threads.append(worker)
        for t in threads:
            t.join()
        while not self.processQueue.empty():
            results.append(self.processQueue.get())
        
        for result in results:
            self.mergeDatabaseGenes(result['genes'])
            self.mergeDatabaseFusions(result['fusions'])
    
    def mergeDatabaseGenes(self,data):
        import heapq
        result=list(heapq.merge(self.genes,data))
        from collections import OrderedDict
        self.genes=list(OrderedDict.fromkeys(result))
    
    def mergeDatabaseFusions(self,data):
        import heapq
        result=list(heapq.merge(self.fusions,data))
        from collections import OrderedDict
        self.fusions=list(OrderedDict.fromkeys(result))
        
    def readDatabase(self,plugin):
        plugin.setDiseasesList(self.diseasesList)
        dataUnsorted =plugin.getData()
        data={}
        data['genes']=sorted(dataUnsorted['genes'], key=lambda gene: gene.name)
        data['fusions']=sorted(dataUnsorted['fusions'], key=lambda fusion: fusion.geneA)
        self.processQueue.put(data)
        self.processQueue.task_done()
        

    def readDatabases(self):
        import xlrd
        self.genes=[]
        self.genesHeaders=[]
        self.fusions=[]
        self.fusionsHeaders=[]
        try:
         	book= xlrd.open_workbook(self.databasePath)
        except:
		raise Exception('cannot open file')
        else:
            for sheet in book.sheets():
                if sheet.name=="genes":
                    num=0
                    for rownum in range(sheet.nrows):
                        myRow = sheet.row_values(rownum)
                        if num ==0:
                            self.genesHeaders=myRow
                        else:
                            gene =Gene(myRow[0],myRow[1],myRow[2],myRow[3],myRow[4])
                            self.genes.append(gene)
                        num+=1
                if sheet.name=="fusions":
                    num=0
                    for rownum in range(sheet.nrows):
                        myRow = sheet.row_values(rownum)
                        if num ==0:
                            self.fusionsHeaders=myRow
                        else:
                            fusion = Fusion(myRow[0],myRow[1],myRow[2],myRow[3],myRow[4],myRow[5],myRow[6],myRow[7],myRow[8])
                            self.fusions.append(fusion)
                        num+=1
    
    def writeDatabase(self):
        import xlwt
        bookToAdd = xlwt.Workbook()
        geneSheet= bookToAdd.add_sheet("genes")
        indexY=0
        for header in self.genesHeaders:
            geneSheet.write(0,indexY,header)
            indexY+=1
        indexX=1
        for gene in self.genes:
            row=[gene.name,gene.function,gene.chromosome,gene.disease,gene.locus]
            indexY=0
            for col in row:
                geneSheet.write(indexX,indexY,row[indexY])
                indexY+=1
            indexX+=1
        geneSheet= bookToAdd.add_sheet("fusions")
        indexY=0
        for header in self.fusionsHeaders:
            geneSheet.write(0,indexY,header)
            indexY+=1
        indexX=1
        for fusion in self.fusions:
            row=[fusion.geneA,fusion.geneB,fusion.informationA.breakpoint, fusion.informationA.strand, fusion.informationA.fusionDirection,fusion.informationB.breakpoint, fusion.informationB.strand, fusion.informationB.fusionDirection,fusion.notes]
            indexY=0
            for col in row:
                geneSheet.write(indexX,indexY,row[indexY])
                indexY+=1
            indexX+=1
        bookToAdd.save(self.databasePath)

